/*
 * ThermistorTablePlot.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   ThermistorTablePlot.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   21 jan 2013
 */

#include <stdexcept>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/functional/hash.hpp>
#include <boost/tuple/tuple.hpp>

#include "Thermistor.hpp"

#include "ThermistorTablePlot.hpp"

using boost::shared_ptr;
using boost::tuple;
using boost::make_tuple;

namespace tltgen {

  ThermistorTablePlot::ThermistorTablePlot(const shared_ptr<ThermistorTable>& thermistorTable, const shared_ptr<LeastSquaresSolver>& solver) :
    thermistorTable(thermistorTable),
    solver(solver)
  {
    if (! isValidThermistorTable(thermistorTable)) {
      throw std::invalid_argument("The given thermistor table is invalid for this plot.");
    }

    thermistorTable->connectTableChangedObserver(boost::bind(&ThermistorTablePlot::onThermistorTableChanged, this, _1, _2));
  }

  ThermistorTablePlot::~ThermistorTablePlot()
  { }

  bool ThermistorTablePlot::isValidThermistorTable(const shared_ptr<ThermistorTable>& thermistorTable)
  {
    return thermistorTable.get() != NULL;
  }

  bool ThermistorTablePlot::isValidLeastSquaresSolver(const shared_ptr<LeastSquaresSolver>& solver)
  {
    return solver.get() != NULL;
  }

  bool ThermistorTablePlot::isValidThermistorModel(const shared_ptr<ThermistorModel>& thermistorModel) const
  {
    return thermistorModel.get() != NULL;
  }


  void ThermistorTablePlot::addThermistorModel(const shared_ptr<ThermistorModel>& thermistorModel)
  {
    if (! isValidThermistorModel(thermistorModel)) {
      throw std::invalid_argument("The given thermistor model is invalid.");
    }

    this->thermistorModels.push_back(thermistorModel);
    signalNbDatasetsChanged(DATASET_ADDED, getNbDatasets());
  }

  void ThermistorTablePlot::removeThermistorModel(const shared_ptr<ThermistorModel const>& thermistorModel)
  {
    unsigned int i = 1;
    std::vector<shared_ptr<ThermistorModel> >::iterator it = this->thermistorModels.begin();
    while (it != this->thermistorModels.end()) {
      if ((*it) == thermistorModel) {
	it = this->thermistorModels.erase(it);
	signalNbDatasetsChanged(DATASET_REMOVED, i);
	return;
      } else {
	++it;
      }
      ++i;
    }
  }

  tuple<PLFLT, PLFLT> ThermistorTablePlot::getXBoundsAuto() const
  {
    PLFLT min = std::numeric_limits<float>::infinity();
    PLFLT max = -std::numeric_limits<float>::infinity();
    for (std::list<ThermistorTableRow *>::const_iterator it = this->thermistorTable->begin(); it != this->thermistorTable->end(); ++it) {
      if (isValidRow(*it)) {
	PLFLT value = getXValue(*it);
	if (value < min) {
	  min = value;
	}

	if (value > max) {
	  max = value;
	}
      }
    }
    return tuple<PLFLT,PLFLT>(min,max);
  }

  tuple<PLFLT, PLFLT> ThermistorTablePlot::getYBoundsAuto() const
  {
    PLFLT min = std::numeric_limits<float>::infinity();
    PLFLT max = -std::numeric_limits<float>::infinity();
    for (std::list<ThermistorTableRow *>::const_iterator it = this->thermistorTable->begin(); it != this->thermistorTable->end(); ++it) {
      if (isValidRow(*it)) {
	PLFLT value = getYValue(*it);
	if (value < min) {
	  min = value;
	}

	if (value > max) {
	  max = value;
	}
      }
    }
    return tuple<PLFLT,PLFLT>(min,max);
  }

  PLFLT ThermistorTablePlot::getYValue(unsigned int i, PLFLT x) const
  {
    if (! isValidDatasetIndex(i)) {
      throw std::invalid_argument("Index '" + boost::lexical_cast<std::string>(i) + 
			     "' is not a valid dataset index for this plot.");
    }

    if (i == 0) {
      // TODO: find nearest table point
      return NAN;
    } else {
      try {
	shared_ptr<Thermistor> thermistor = this->thermistorModels.at(i-1)->generate(getThermistorTable(), *getLeastSquaresSolver().get());
	PLFLT y = getYValue(thermistor, x);
	return y;
      } catch (const std::exception& e) {
	return NAN;
      }
    }
  }
 
  shared_ptr<ThermistorTable> ThermistorTablePlot::getThermistorTable() const
  {
    return this->thermistorTable;
  }

  shared_ptr<LeastSquaresSolver> ThermistorTablePlot::getLeastSquaresSolver() const
  {
    return this->solver;
  }

    
  unsigned int ThermistorTablePlot::getNbDatasets() const
  {
    return this->thermistorModels.size() + 1;
  }
  
  std::string ThermistorTablePlot::getDatasetName(unsigned int i) const
  {
    if (! isValidDatasetIndex(i)) {
      throw std::invalid_argument("Index '" + boost::lexical_cast<std::string>(i) + 
			     "' is not a valid dataset index for this plot.");
    }

    if (i == 0) {
      return "Table";
    } else {
      return this->thermistorModels.at(i-1)->getName();
    }
  }

  void ThermistorTablePlot::plotViewport(wxPLplotstream *pls)
  {
    PLFLT xmin = getXBounds().get<0>(), xmax = getXBounds().get<1>();
    PLFLT ymin = getYBounds().get<0>(), ymax = getYBounds().get<1>();

    pls->wid(2); // Select pen width 2
    pls->col0(1); // Select color (red)
    pls->sxax(getXMaxDigits(), 0);
    pls->syax(getYMaxDigits(), 0);
    pls->env0(xmin, xmax, ymin, ymax, 0, 0);    

    pls->col0(2); // Select color (yellow)
    pls->lab(getXLabel().c_str(), getYLabel().c_str(), "");
  }

  void ThermistorTablePlot::plotDataset(wxPLplotstream *pls, unsigned int i)
  {
    if (! isValidDatasetIndex(i)) {
      throw std::invalid_argument("Index '" + boost::lexical_cast<std::string>(i) + 
			     "' is not a valid dataset index for this plot.");
    }

    if (i == 0) {
      plotTableDataset(pls);
    } else {
      plotThermistorModel(pls, this->thermistorModels.at(i-1));
    }
  }

  void ThermistorTablePlot::plotTableDataset(wxPLplotstream *pls)
  {
    shared_ptr<ThermistorTable> t = this->thermistorTable;
    size_t np = 0;
    for (std::list<ThermistorTableRow *>::const_iterator ci = t->begin(); ci != t->end(); ++ci) {
      ThermistorTableRow *tableRow = *ci;
      if (isValidRow(tableRow)) {
	np += 1;
      }
    }

    PLFLT x[np], y[np];
    unsigned int i = 0;
    for (std::list<ThermistorTableRow *>::const_iterator ci = t->begin(); ci != t->end(); ++ci) {
      ThermistorTableRow *tableRow = *ci;
      if (isValidRow(tableRow)) {
	x[i] = getXValue(tableRow);
	y[i] = getYValue(tableRow);
        i += 1;
      }
    }

    pls->col0(3); // Select color for line (green)
    pls->wid(2); // Select pen width 2
    pls->string(np, x, y, "o");
  }

  bool ThermistorTablePlot::compareFirst(tuple<PLFLT,PLFLT> t1, tuple<PLFLT,PLFLT> t2)
  {
    return t1.get<0>() < t2.get<0>();
  }

  void ThermistorTablePlot::sort(PLFLT x[], PLFLT y[], size_t np) const
  {
    tuple<PLFLT,PLFLT> pairs[np];

    for (unsigned int i = 0; i < np; ++i) {
      pairs[i] = make_tuple(x[i], y[i]);
    }
    
    std::sort(pairs, &pairs[np], compareFirst);

    for (unsigned int i = 0; i < np; ++i) {
      x[i] = pairs[i].get<0>();
      y[i] = pairs[i].get<1>();
    }
  }

  void ThermistorTablePlot::plotThermistorModel(wxPLplotstream *pls, const shared_ptr<ThermistorModel>& thermistorModel)
  {
    try {
      size_t np_t = 0;
      for (std::list<ThermistorTableRow *>::const_iterator ci = this->thermistorTable->begin(); ci != this->thermistorTable->end(); ++ci) {
	ThermistorTableRow *tableRow = *ci;
	if (isValidRow(tableRow)) {
	  np_t += 1;
	}
      }
      
      shared_ptr<Thermistor> thermistor = thermistorModel->generate(getThermistorTable(), *getLeastSquaresSolver().get());
      const size_t np = 60; //TODO: make dependent on width
      const PLFLT increment = (getXBounds().get<1>() - getXBounds().get<0>())/(np-1);
      PLFLT x[np + np_t], y[np + np_t];

      unsigned int i;
      PLFLT currentX = getXBounds().get<0>();
      for (i = 0; i < np; ++i) {
	x[i] = currentX;
	try {
	  y[i] = getYValue(thermistor, x[i]);
	} catch (std::exception& e) {
	  y[i] = NAN;
	}
	currentX += increment;
      }

      // Add points at the x values defined in the table
      for (std::list<ThermistorTableRow *>::const_iterator ci = this->thermistorTable->begin(); ci != this->thermistorTable->end(); ++ci) {
	ThermistorTableRow *tableRow = *ci;
	if (isValidRow(tableRow)) {
	  x[i] = getXValue(tableRow);
	  try {
	    y[i] = getYValue(thermistor, x[i]);
	  } catch (std::exception& e) {
	    y[i] = NAN;
	  }
	  i += 1;
	}
      }

      sort(x, y, np + np_t);
      boost::hash<std::string> string_hash;
      const int color = 4 + (string_hash(thermistorModel->getName()) % 12);
      pls->col0(color); // Select color for line
      pls->wid(2); // Select pen width 2
      pls->line(np + np_t, x, y);
    } catch (std::exception& e) {
      std::cout << "Error plotting thermistor model '" << thermistorModel->getName() <<"': " << e.what() << std::endl;
    }
  }


  void ThermistorTablePlot::thermistorTableUpdated()
  {
    if (isXBoundsAuto()) {
      setXBoundsAuto(false);
    }
    if (isYBoundsAuto()) {
      setYBoundsAuto(false);
    }
    signalDataChanged();
  }

  void ThermistorTablePlot::onThermistorTableChanged(ThermistorTable::Event e, ThermistorTableRow *tableRow)
  {
    switch (e) {
    case ThermistorTable::ROW_ADDED:
    case ThermistorTable::ROW_CHANGED:
    case ThermistorTable::ROW_REMOVED:
    case ThermistorTable::BATCH_ENDED:
      thermistorTableUpdated();
      break;
    default:
      break;
    }
  }

}

